/*
 * $Id: sclex.c 245 2007-04-12 10:07:57Z ludovic.rousseau $
 *
 * Copyright (C) 2003
 *  Jamie Honan <jhonan@optusnet.com.au>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
 * MA 02110-1301, USA.
 */
#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif // HAVE_CONFIG_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include "nfcconf.h"
#include "internal.h"

typedef struct {
    char *buf;
    size_t bufmax;
    size_t bufcur;
    int saved_char;
    const char *saved_string;
    FILE *fp;
} BUFHAN;

static void buf_init(BUFHAN * bp, FILE * fp, const char *saved_string) {
    bp->fp = fp;
    bp->saved_char = 0;
    bp->buf = (char *) malloc(256);
    bp->bufmax = 256;
    bp->bufcur = 0;
    bp->buf[0] = '\0';
    bp->saved_string = saved_string;
}

static void buf_addch(BUFHAN * bp, char ch) {
    if (bp->bufcur >= bp->bufmax) {
        bp->bufmax += 256;
        bp->buf = (char *) realloc(bp->buf, bp->bufmax);
    }
#if 0
    printf("pushback %c\n", ch);
#endif
    bp->buf[bp->bufcur++] = ch;
    bp->buf[bp->bufcur] = '\0';
}

static int buf_nextch(BUFHAN * bp) {
    int saved;

    if (bp->saved_char) {
        saved = bp->saved_char;
        bp->saved_char = 0;
        return saved;
    }
    if (bp->saved_string) {
        if (*(bp->saved_string) == '\0')
            return EOF;
        saved = (unsigned char) (*(bp->saved_string++));
        return saved;
    } else {
        saved = fgetc(bp->fp);
        return saved;
    }
}

static void buf_finished(BUFHAN * bp) {
    if (bp->buf) {
        free(bp->buf);
        bp->buf = NULL;
    }
}

static void buf_eat_till(BUFHAN * bp, char start, const char *end) {
    int i;

    if (start) {
        buf_addch(bp, start);
    }
    while (1) {
        i = buf_nextch(bp);
        if (i == EOF)
            return;
        if (strchr(end, i)) {
            bp->saved_char = i;
            return;
        }
        buf_addch(bp, (char) i);
    }
}

static void buf_zero(BUFHAN * bp) {
    bp->bufcur = 0;
    bp->buf[0] = '\0';
}

static int nfcconf_lex_engine(nfcconf_parser * parser, BUFHAN * bp) {
    int this_char;

    while (1) {
        switch (this_char = buf_nextch(bp)) {
        case '#':
            /* comment till end of line */
            buf_eat_till(bp, '#', "\r\n");
            nfcconf_parse_token(parser, TOKEN_TYPE_COMMENT, bp->buf);
            buf_zero(bp);
            continue;
        case '\n':
            nfcconf_parse_token(parser, TOKEN_TYPE_NEWLINE, NULL);
            continue;
        case ' ':
        case '\t':
        case '\r':
            /* eat up whitespace */
            continue;
        case ',':
        case '{':
        case '}':
        case '=':
        case ';':
            buf_addch(bp, (char) this_char);
            nfcconf_parse_token(parser, TOKEN_TYPE_PUNCT, bp->buf);
            buf_zero(bp);
            continue;
        case '"':
            buf_eat_till(bp, (char) this_char, "\"\r\n");
            buf_addch(bp, (char) buf_nextch(bp));
            nfcconf_parse_token(parser, TOKEN_TYPE_STRING, bp->buf);
            buf_zero(bp);
            continue;
        case EOF:
            break;
        default:
            buf_eat_till(bp, (char) this_char, ";, \t\r\n");
            nfcconf_parse_token(parser, TOKEN_TYPE_STRING, bp->buf);
            buf_zero(bp);
            continue;
        }
        break;
    }
    buf_finished(bp);
    return 1;
}

int nfcconf_lex_parse(nfcconf_parser * parser, const char *filename) {
    FILE *fp;
    BUFHAN bhan;
    int ret;

    fp = fopen(filename, "r");
    if (!fp) {
        parser->error = 1;
        snprintf(parser->emesg, sizeof(parser->emesg),
                 "File %s can't be opened\n", filename);
        return 0;
    }
    buf_init(&bhan, fp, (char *) NULL);
    ret = nfcconf_lex_engine(parser, &bhan);
    fclose(fp);
    return ret;
}

int nfcconf_lex_parse_string(nfcconf_parser * parser, const char *string) {
    BUFHAN bhan;
    int ret;

    buf_init(&bhan, (FILE *) NULL, string);
    ret = nfcconf_lex_engine(parser, &bhan);
    return ret;
}
